package com.tsing.cli.common.security;

import com.tsing.cli.common.core.util.SpringUtils;
import com.tsing.cli.common.security.annotation.AnonymousAccess;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.*;

/**
 * Security配置
 *
 * @author TheTsing
 */
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityAutoConfiguration {

    /**
     * 密码加密和验证策略
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 手动注入AuthenticationManager
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * HttpSecurity配置
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        RequestMappingHandlerMapping requestMappingHandlerMapping = SpringUtils.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
        return httpSecurity
                .csrf(AbstractHttpConfigurer::disable)
                .headers(AbstractHttpConfigurer::disable)
                .sessionManagement(AbstractHttpConfigurer::disable)
                .exceptionHandling((exceptionHandling) -> exceptionHandling
                        .authenticationEntryPoint((request, response, authException) -> {
                            response.setContentType(MediaType.TEXT_PLAIN_VALUE);
                            response.setStatus(HttpStatus.UNAUTHORIZED.value());
                            response.getWriter().write(HttpStatus.UNAUTHORIZED.getReasonPhrase());
                        }))
                .authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
                        .requestMatchers("/websocket/**", "/error", "/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll()
                        .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
                        .requestMatchers(HttpMethod.GET, anonymousUrls.get("GET").toArray(new String[0])).permitAll()
                        .requestMatchers(HttpMethod.POST, anonymousUrls.get("POST").toArray(new String[0])).permitAll()
                        .requestMatchers(HttpMethod.PUT, anonymousUrls.get("PUT").toArray(new String[0])).permitAll()
                        .requestMatchers(HttpMethod.PATCH, anonymousUrls.get("PATCH").toArray(new String[0])).permitAll()
                        .requestMatchers(HttpMethod.DELETE, anonymousUrls.get("DELETE").toArray(new String[0])).permitAll()
                        .requestMatchers(anonymousUrls.get("ALL").toArray(new String[0])).permitAll()
                        .anyRequest().authenticated())
//                .addFilterBefore(new TokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
        Map<String, Set<String>> anonymousUrls = new HashMap<>(8);
        Set<String> get = new HashSet<>();
        Set<String> post = new HashSet<>();
        Set<String> put = new HashSet<>();
        Set<String> patch = new HashSet<>();
        Set<String> delete = new HashSet<>();
        Set<String> all = new HashSet<>();
        handlerMethodMap.forEach((k, v) -> {
            AnonymousAccess anonymousAccess = v.getMethodAnnotation(AnonymousAccess.class);
            if (Objects.nonNull(anonymousAccess)) {
                List<RequestMethod> requestMethods = new ArrayList<>(k.getMethodsCondition().getMethods());
                if (requestMethods.isEmpty()) {
                    all.addAll(k.getPathPatternsCondition().getPatternValues());
                } else {
                    switch (requestMethods.get(0)) {
                        case GET -> get.addAll(k.getPathPatternsCondition().getPatternValues());
                        case POST -> post.addAll(k.getPathPatternsCondition().getPatternValues());
                        case PUT -> put.addAll(k.getPathPatternsCondition().getPatternValues());
                        case PATCH -> patch.addAll(k.getPathPatternsCondition().getPatternValues());
                        case DELETE -> delete.addAll(k.getPathPatternsCondition().getPatternValues());
                        default -> all.addAll(k.getPathPatternsCondition().getPatternValues());
                    }
                }
            }
        });
        anonymousUrls.put("GET", get);
        anonymousUrls.put("POST", post);
        anonymousUrls.put("PUT", put);
        anonymousUrls.put("PATCH", patch);
        anonymousUrls.put("DELETE", delete);
        anonymousUrls.put("ALL", all);
        return anonymousUrls;
    }

}
